package com.pricetag.search;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.StreamCorruptedException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Log;

import com.pricetag.exceptions.SearchRecordNotExistsException;
import com.pricetag.googleproductsearch.GoogleShopPriceSearch;
import com.pricetag.googleproductsearch.GoogleShopPriceSearchResult;
import com.pricetag.helpers.ApplicationSettings;
import com.pricetag.helpers.Constants;
import com.pricetag.helpers.Utils;
import com.pricetag.iqengine.IQEngineAdapter;
import com.pricetag.iqengine.IQEngineResultHandler;
import com.pricetag.iqengine.IQEngineResultStore;
import com.pricetag.pricetagandroid.R;
import com.pricetag.serversdk.LocalImageDBSearchResult;
import com.pricetag.serversdk.ServerProxy;

public class SearchHandler {

	private Context context = null;
	private Activity activity = null;
	private String searchRecordID = null;
	private String imageFilename = null;
	private File imageFile = null;
	private Date dateCreated = null;
	private String remoteName = null;
	private boolean hasFinishedSavingImage = false;
	private SharedPreferences sharedPref = null;	
	private BackupThread backupThread = new BackupThread();
	private RestoreThread restoreThread = new RestoreThread();

	private static ServerProxy serverProxy = new ServerProxy();
	private static String backupFilename = "search_records_backup.data";
	private static boolean useIQEngine = true;
	private static boolean useGoogleGoggleAPI = ApplicationSettings.useGoogleGoggleAPI;
	private static boolean useLocalImageDB = ApplicationSettings.useLocalImageDB;
	private static SortedMap<String, SearchRecord> searchResults = new TreeMap<String, SearchRecord>();
	private static IQEngineAdapter iqEngineAdapter = null;
	private static HandleIQEngineResultThread handleIQEngineResultThread = null;

	public SearchHandler(Context context) {
		this.context = context;
		if (this.context instanceof Activity) {
			this.activity = (Activity) this.context;
		}

		if (iqEngineAdapter == null) {
			if (this.activity != null) {
				iqEngineAdapter = new IQEngineAdapter((Activity) this.context, new IQEngineResultHandler());
			}
		}
		PreferenceManager.setDefaultValues(context, R.xml.preferences, false);
		sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
		useIQEngine = sharedPref.getBoolean("useIQEngine", ApplicationSettings.useIQEngine);
		
		this.restoreThread.start();
		
	}

	public SearchHandler(String searchRecordID, Context context) throws SearchRecordNotExistsException {
		this.context = context;
		if (this.context instanceof Activity) {
			this.activity = (Activity) this.context;
		}

		if (SearchHandler.getSearchRecord(searchRecordID) == null) {
			throw new SearchRecordNotExistsException();
		}

		if (iqEngineAdapter == null) {
			if (this.activity != null) {
				iqEngineAdapter = new IQEngineAdapter((Activity) this.context, new IQEngineResultHandler());
			}
		}
		PreferenceManager.setDefaultValues(context, R.xml.preferences, false);
		sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
		useIQEngine = sharedPref.getBoolean("useIQEngine", ApplicationSettings.useIQEngine);

		this.searchRecordID = searchRecordID;
		this.imageFile = new File(context.getFilesDir(), getSearchRecord(searchRecordID).getImageFilename());		
	}

	public void saveImageAndStartImageSearch(final byte[] imageData) {
		new Thread(new Runnable() {
			@Override
			public void run() {
				saveImage(imageData);
				while (!hasFinishedSavingImage) {
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
					}
				}
				doImageSearch();
				backupThread.start();
			}

		}).start();
	}

	public void startImageSearchAgain() {
		if (this.searchRecordID == null) {
			return;
		}
		new Thread(new Runnable() {
			@Override
			public void run() {
				doImageSearch();
			}

		}).start();
	}

	private void doImageSearch() {

		SearchRecord searchRecord = SearchHandler.getSearchRecord(this.searchRecordID);
		synchronized (searchResults) {
			searchRecord.setPerformedLocalImageDBSearch(false);
		}

		if (useIQEngine) {
			performIQEngineSearchForProductName();
		}
		
		setRemoteName();

		if (remoteName != null) {
			if (useGoogleGoggleAPI) {
				performGoogleGoggleAPISearchForProductName();
			}
			if (useLocalImageDB) {
				performLocalImageDBSearchForProductName();
			}
		}

		if (searchResults.size() - Constants.SEARCH_RECORD_MAX_STORE_NUMBER > 5) {
			Thread houseKeepThread = new HouseKeepSearchRecordHistoryThread();
			houseKeepThread.start();
		}
	}

	public void searchLocalImageDBWithCategory(String category) {
		this.performLocalImageDBSearchForProductNameWithCategory(this.searchRecordID, category);
	}

	public void searchGoogleShopWithCustomName(String customName) {
		if (customName == null || customName.length() == 0) {
			return;
		}
		SearchRecord searchRecord = SearchHandler.getSearchRecord(this.searchRecordID);
		synchronized (searchResults) {
			searchRecord.setCustomProductName(customName);
		}
		this.performGoogleShopPriceSearch("Custom");
	}

	public static SearchRecord getSearchRecord(String searchRecordID) {
		return searchResults.get(searchRecordID);
	}

	public static SearchRecord[] getSearchRecords() {

		SearchRecord[] allRecords = getAllSearchRecords();
		ArrayList<SearchRecord> tempList = new ArrayList<SearchRecord>();
		for (SearchRecord record : allRecords) {
			if (!record.isDeleted()) {
				tempList.add(record);
			}
		}

		SearchRecord[] searchRecords = new SearchRecord[tempList.size()];
		for (int i = 0; i < tempList.size(); i++) {
			searchRecords[i] = tempList.get(i);
		}

		return searchRecords;

	}

	public static SearchRecord[] getAllSearchRecords() {

		Collection<SearchRecord> values = searchResults.values();
		Object[] array = values.toArray();
		SearchRecord[] searchRecords = new SearchRecord[array.length];
		for (int i = 0; i < searchRecords.length; i++) {
			searchRecords[i] = (SearchRecord) array[array.length - 1 - i];
		}
		return searchRecords;

	}

	private static String generateSearchRecordID() {
		Calendar now = new GregorianCalendar();
		StringBuilder filenameBuilder = new StringBuilder();
		filenameBuilder
				.append(now.get(Calendar.YEAR))
				.append("-")
				.append(now.get(Calendar.MONTH))
				.append("-")
				.append(now.get(Calendar.DATE))
				.append("-")
				.append(now.get(Calendar.HOUR_OF_DAY) * 3600 + now.get(Calendar.MINUTE) * 60 + now.get(Calendar.SECOND));
		return filenameBuilder.toString();
	}

	private String saveImage(final byte[] imageData) {
		this.searchRecordID = generateSearchRecordID();
		this.dateCreated = new GregorianCalendar().getTime();
		this.imageFilename = searchRecordID + ".image";
		this.imageFile = new File(context.getFilesDir(), this.imageFilename);
		new Thread(new Runnable() {
			@Override
			public void run() {
				if (imageFile.exists()) {
					imageFile.delete();
				}
				FileOutputStream fos = null;
				try {
					fos = new FileOutputStream(imageFile.getPath());
					fos.write(imageData);
					SearchRecord searchResult = new SearchRecord(searchRecordID);
					searchResult.setThumbnail(Utils.createThumbnail(imageData));
					searchResult.setDateCreated(dateCreated);
					searchResult.setImageFilename(imageFilename);
					synchronized (searchResults) {
						searchResults.put(searchRecordID, searchResult);
					}
					hasFinishedSavingImage = true;
				} catch (java.io.IOException e) {
				} finally {
					if (fos != null) {
						try {
							fos.close();
						} catch (IOException e) {
							e.printStackTrace();
						}
					}
				}
			}
		}).start();
		return searchRecordID;
	}

	private void performLocalImageDBSearchForProductName() {
		if (this.searchRecordID != null) {
			performLocalImageDBSearchForProductName(this.searchRecordID);
		}
	}

	private void performLocalImageDBSearchForProductName(String searchRecordID) {
		SearchRecord searchRecord = searchResults.get(searchRecordID);
		if (searchRecord.isPerformedLocalImageDBSearch()) {
			return;
		}
		String pseudoCategory = null;
		if (searchRecord.getIqEngineProductName() != null && searchRecord.getIqEngineProductName().length() != 0) {
			pseudoCategory = searchRecord.getIqEngineProductName();
		} else if (searchRecord.getGoogleGoggleProductName() != null
				&& searchRecord.getGoogleGoggleProductName().length() != 0) {
			pseudoCategory = searchRecord.getGoogleGoggleProductName();
		}
		this.performLocalImageDBSearchForProductNameWithCategory(searchRecordID, pseudoCategory);

	}

	private void performLocalImageDBSearchForProductNameWithCategory(final String searchRecordID,
			final String definedCategory) {
		if (definedCategory == null || definedCategory.length() == 0) {
			return;
		}
		new Thread(new Runnable() {
			@Override
			public void run() {
				SearchRecord searchRecord = searchResults.get(searchRecordID);
				LocalImageDBSearchResult localSearchResult = null;

				synchronized (searchResults) {
					searchRecord.setPerformedLocalImageDBSearch(true);
				}

				setRemoteName();
				localSearchResult = serverProxy.searchLocalImage(remoteName, definedCategory);

				if (localSearchResult == null) {
					return;
				}
				synchronized (searchResults) {
					if (searchRecord != null) {
						searchRecord.setLocalImageDBSearchResult(localSearchResult);
						searchRecord.setLocalImageDBProductName(localSearchResult.getName());
					}
				}
				performGoogleShopPriceSearch("Local");
			}
		}).start();
	}

	private void performGoogleGoggleAPISearchForProductName() {
		new Thread(new Runnable() {
			@Override
			public void run() {
				setRemoteName();
				String googleGoggleProductName = serverProxy.getImageNameFromGoogle(remoteName);
				if (googleGoggleProductName != null && googleGoggleProductName.length() != 0) {
					synchronized (searchResults) {
						SearchRecord searchRecord = searchResults.get(searchRecordID);
						if (searchRecord != null) {
							searchRecord.setGoogleGoggleProductName(googleGoggleProductName);
						}
					}
					performGoogleShopPriceSearch("Google");
					performLocalImageDBSearchForProductName();
				}
			}
		}).start();
	}

	private void performIQEngineSearchForProductName() {
		if (iqEngineAdapter != null) {
			iqEngineAdapter.performRemoteSearch(imageFile, this.searchRecordID);
		}
		if (handleIQEngineResultThread == null || !handleIQEngineResultThread.isAlive()) {
			handleIQEngineResultThread = new HandleIQEngineResultThread("HandleIQEngineResultThread");
			handleIQEngineResultThread.start();
		}
	}

	private class HandleIQEngineResultThread extends Thread {

		public HandleIQEngineResultThread(String threadName) {
			super(threadName);
		}

		@Override
		public void run() {
			try {
				Thread.sleep(3000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			while (IQEngineResultStore.hasMoreLeft()) {
				Map<String, String> results = IQEngineResultStore.getReadyResults();
				if (results.size() > 0) {
					for (String tempSearchRecordID : results.keySet()) {
						synchronized (searchResults) {
							SearchRecord tempSearchRecord = searchResults.get(tempSearchRecordID);
							if (tempSearchRecord != null) {
								tempSearchRecord.setIqEngineProductName(results.get(tempSearchRecordID));
								Log.w("SearchResult", results.get(tempSearchRecordID));
							}
						}
						performGoogleShopPriceSearch("IQEngine", tempSearchRecordID);
						performLocalImageDBSearchForProductName(tempSearchRecordID);
					}
					try {
						Thread.sleep(3000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}

		}
	}

	private void performGoogleShopPriceSearch(String method) {
		if (this.searchRecordID != null) {
			performGoogleShopPriceSearch(method, this.searchRecordID);
		}
	}

	@SuppressLint("DefaultLocale")
	private void performGoogleShopPriceSearch(String method, String searchRecordID) {
		if (method == null || method.length() <= 0) {
			method = "ALL";
		}
		method = method.toUpperCase();
		char caseLetter = method.charAt(0);
		new GoogleShopPriceSearchThread(caseLetter, searchRecordID).start();
	}

	private static class GoogleShopPriceSearchThread extends Thread {

		ArrayList<GoogleShopPriceSearchResult> localNameSearchResults, googleNameSearchResults,
				iqEngineNameSearchResults, customNameSearchResults;
		private char caseLetter;
		private String searchRecordID;

		public GoogleShopPriceSearchThread(char caseLetter, String searchRecordID) {
			this.caseLetter = caseLetter;
			this.searchRecordID = searchRecordID;
		}

		@Override
		public void run() {
			switch (caseLetter) {
			case 'L':
				searchUsingLocalProductName();
				break;
			case 'G':
				searchUsingGoogleProductName();
				break;
			case 'I':
				searchUsingIQEngineProductName();
				break;
			case 'C':
				searchUsingCustomProductName();
				break;
			default:
				searchUsingLocalProductName();
				searchUsingGoogleProductName();
				searchUsingIQEngineProductName();
				searchUsingCustomProductName();
			}

			saveResults();
		}

		private void searchUsingLocalProductName() {
			String localProductName = searchResults.get(searchRecordID).getLocalImageDBProductName();
			if (localProductName == null || localProductName.length() <= 0) {
				return;
			} else {
				localNameSearchResults = new GoogleShopPriceSearch().getPrice(localProductName,
						ApplicationSettings.countryCode);
			}
		}

		private void searchUsingGoogleProductName() {
			String googleProductName = searchResults.get(searchRecordID).getGoogleGoggleProductName();
			if (googleProductName == null || googleProductName.length() <= 0) {
				return;
			} else {
				googleNameSearchResults = new GoogleShopPriceSearch().getPrice(googleProductName,
						ApplicationSettings.countryCode);
			}
		}

		private void searchUsingIQEngineProductName() {
			String iqEngineProductName = searchResults.get(searchRecordID).getIqEngineProductName();
			if (iqEngineProductName == null || iqEngineProductName.length() <= 0) {
				return;
			} else {
				iqEngineNameSearchResults = new GoogleShopPriceSearch().getPrice(iqEngineProductName,
						ApplicationSettings.countryCode);
			}
		}

		private void searchUsingCustomProductName() {
			String customProductName = searchResults.get(searchRecordID).getCustomProductName();
			if (customProductName == null || customProductName.length() <= 0) {
				return;
			} else {
				customNameSearchResults = new GoogleShopPriceSearch().getPrice(customProductName,
						ApplicationSettings.countryCode);
			}
		}

		private void saveResults() {
			synchronized (searchResults) {
				SearchRecord searchRecord = searchResults.get(searchRecordID);
				if (this.localNameSearchResults != null && this.localNameSearchResults.size() > 0) {
					searchRecord.setLocalNameGoogleShopSearchResults(localNameSearchResults);
				}
				if (this.googleNameSearchResults != null && this.googleNameSearchResults.size() > 0) {
					searchRecord.setGoogleNameGoogleShopSearchResults(googleNameSearchResults);
				}
				if (this.iqEngineNameSearchResults != null && this.iqEngineNameSearchResults.size() > 0) {
					searchRecord.setIqEngineNameGoogleShopSearchResults(iqEngineNameSearchResults);
				}
				if (this.customNameSearchResults != null && this.customNameSearchResults.size() > 0) {
					searchRecord.setCustomNameGoogleShopSearchResults(customNameSearchResults);
				}
			}
		}

	}

	private void setRemoteName() {
		if (remoteName == null) {
			SearchRecord searchRecord = SearchHandler.getSearchRecord(searchRecordID);
			if (searchRecord.getRemoteName() != null) {
				remoteName = searchRecord.getRemoteName();
			} else {
				try {
					remoteName = serverProxy.uploadImage(imageFile);
					synchronized (searchResults) {
						searchResults.get(searchRecordID).setRemoteName(remoteName);
					}
				} catch (URISyntaxException e) {
					e.printStackTrace();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}

	private class HouseKeepSearchRecordHistoryThread extends Thread {
		@Override
		public void run() {
			synchronized (searchResults) {
				if (searchResults.size() > Constants.SEARCH_RECORD_MAX_STORE_NUMBER) {
					int count = searchResults.size() - Constants.SEARCH_RECORD_MAX_STORE_NUMBER;
					for (int i = 0; i < count; i++) {
						String tempSearchRecordID = searchResults.firstKey();
						searchResults.remove(tempSearchRecordID);
					}
				}
			}

			File folder = context.getFilesDir();
			File[] files = folder.listFiles();
			if (files != null) {
				for (File file : files) {
					if (file.getName().endsWith(".image")) {
						int indexOfDot = file.getName().indexOf(".image");
						String searchRecordID = file.getName().substring(0, indexOfDot);
						if (!searchResults.containsKey(searchRecordID)) {
							file.delete();
						}
					}
				}
			}
		}
	}

	private class BackupThread extends Thread {
		@Override
		public void run() {
			File backupFile = new File(context.getFilesDir(), backupFilename);
			if (!backupFile.exists()) {
				try {
					backupFile.createNewFile();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}

			if (searchResults.size() > 0) {
				synchronized (searchResults) {
					ObjectOutputStream oout = null;
					try {
						oout = new ObjectOutputStream(new FileOutputStream(backupFile));
						oout.writeObject(searchResults);
					} catch (FileNotFoundException e) {
						e.printStackTrace();
					} catch (IOException e) {
						e.printStackTrace();
					} catch (Exception e) {
						e.printStackTrace();
					}
				}

			}
		}
	}

	private class RestoreThread extends Thread {
		@Override
		public void run() {
			File backupFile = new File(context.getFilesDir(), backupFilename);
			if (!backupFile.exists()) {
				try {
					backupFile.createNewFile();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}

			if (searchResults.size() == 0) {
				if (backupFile.exists()) {
					synchronized (searchResults) {
						ObjectInputStream oin = null;
						TreeMap<String, SearchRecord> map = null;
						try {
							oin = new ObjectInputStream(new FileInputStream(backupFile));
							map = (TreeMap<String, SearchRecord>) oin.readObject();
						} catch (StreamCorruptedException e) {
							map = null;
							e.printStackTrace();
						} catch (FileNotFoundException e) {
							map = null;
							e.printStackTrace();
						} catch (IOException e) {
							map = null;
							e.printStackTrace();
						} catch (ClassNotFoundException e) {
							map = null;
							e.printStackTrace();
						} catch (Exception e) {
							map = null;
							e.printStackTrace();
						}

						if (map != null && map.size() > 0) {
							searchResults = map;
						}
					}

				}
			}
		}
	}
}
